﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using D2DEngine.Core;
using D2DEngine.Util;


namespace D2DEngine
{
    public class D2DCamera
    {
        List<Vector2> shakePositions = new List<Vector2>();
        Vector2 beforeShakePosition;

        bool doShake = false;
        float shakeAmount = 4.0f;
        float shakeDuration = 0.2f;

        int shakeIndex = 0;


        protected Vector2 position = Vector2.Zero;

        public Vector2 Position
        {
            get { return this.position; }
            set { this.position = value; }
        }

        /// <summary>
        /// The transformation matrix applied to the rendered objects
        /// </summary>
        public Matrix TransformMatrix
        {
            get { return Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0f)); }
        }

        /// <summary>
        /// Transforms the given screen coordinates
        /// into world coordinates
        /// </summary>
        /// <param name="x">X coordinate</param>
        /// <param name="y">Y coordinate</param>
        /// <returns></returns>
        public Vector3 GetWorldCoordinate(float x, float y)
        {
            return Vector3.Transform(new Vector3(x, y, 1), Matrix.Invert(TransformMatrix));
        }

        public D2DCamera()
        {
            InititializeShake();
        }

        /// <summary>
        /// Initializes the shake positions
        /// </summary>
        private void InititializeShake()
        {
            shakePositions.Add(new Vector2(-shakeAmount, 0));
            shakePositions.Add(new Vector2(shakeAmount, 0));
            shakePositions.Add(new Vector2(0, shakeAmount));
            shakePositions.Add(new Vector2(0, -shakeAmount));
        }

        /// <summary>
        /// Clamps the camera to the specified area
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public void ClampToArea(float width, float height)
        {
            if (this.position.X > width)
                this.position.X = width;

            if (this.position.Y > height)
                this.position.Y = height;

            if (this.position.X < 0)
                this.position.X = 0;
            if (this.position.Y < 0)
                this.position.Y = 0;
        }


        /// <summary>
        /// Clamps the camera to the specified area
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public void ClampToArea(Rectangle viewport, float screenWidth, float screenHeight)
        {            
            if (this.position.X + screenWidth > viewport.Width)
                this.position.X = viewport.Width - screenWidth;

            if (this.position.Y + screenHeight > viewport.Height)
                this.position.Y = viewport.Height - screenHeight;
            
            if (this.position.X < viewport.X)
                this.position.X = viewport.X;
            if (this.position.Y < viewport.Y)
                this.position.Y = viewport.Y;            
        }

        /// <summary>
        /// Locks the camera to follow the target's movements
        /// </summary>
        /// <param name="target"></param>
        /// <param name="screenWidth"></param>
        /// <param name="screenHeight"></param>
        public void LockToTarget(D2DSceneObject target, float screenWidth, float screenHeight)
        {
            if(!this.doShake)
            {
                this.position.X = D2DMath.ToPixels(target.Position.X) - target.Sprite.Width - (screenWidth / 2);
                this.position.Y = D2DMath.ToPixels(target.Position.Y) - target.Sprite.Height - (screenHeight / 2);
            }
        }        

        /// <summary>
        /// Updates the camera
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            if (doShake)
            {
                float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
                shakeDuration -= elapsedTime;

                if (shakeDuration > 0f)
                {
                    ProcessShake();
                }
                else
                {
                    EndShake();
                }
            }
        }
        

        /// <summary>
        /// Starts the shake effect
        /// </summary>
        public void Shake()
        {
            this.doShake = true;
            this.beforeShakePosition = Position;
        }        


        /// <summary>
        /// Processes the shake effect
        /// </summary>
        private void ProcessShake()
        {
            Position += shakePositions[shakeIndex];

            if (shakeIndex < shakePositions.Count - 1)
                shakeIndex++;
            else
                shakeIndex = 0;
        }

        /// <summary>
        /// Ends the shake effect
        /// </summary>
        private void EndShake()
        {
            doShake = false;
            shakeDuration = 0.2f;
            Position = beforeShakePosition;
        }        
    }
}
